package com.supplychain.scm.core.oss;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.supplychain.scm.core.basis.constant.StringPool;
import com.supplychain.scm.core.oss.model.DbyFile;
import com.supplychain.scm.core.oss.model.OssFile;
import com.supplychain.scm.core.oss.props.OssProperties;
import com.supplychain.scm.core.oss.rule.OssRule;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.tika.Tika;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * 阿里云oss操作
 *
 * @author veliger
 * create on 2022-03-14
 */
@AllArgsConstructor
public class AliOssTemplate {
    private AmazonS3 amazonS3;
    private OssProperties ossProperties;
    private OssRule ossRule;


    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            amazonS3.createBucket(bucketName);
        }
    }

    @SneakyThrows
    public void removeBucket(String bucketName) {
        amazonS3.deleteBucket(bucketName);
    }

    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return amazonS3.doesBucketExistV2(bucketName);
    }

    public List<Bucket> allBuckets() {
        try {
            return amazonS3.listBuckets();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SneakyThrows
    public void copyFile(String bucketName, String fileName, String destBucketName) {
        CopyObjectRequest copyObjectArgs = new CopyObjectRequest();
        copyObjectArgs.setSourceBucketName(bucketName);
        copyObjectArgs.setDestinationBucketName(destBucketName);
        copyObjectArgs.setSourceKey(fileName);
        copyObjectArgs.setDestinationKey(fileName);
        amazonS3.copyObject(copyObjectArgs);
    }

    @SneakyThrows
    public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
        CopyObjectRequest copyObjectRequest = new CopyObjectRequest();
        copyObjectRequest.setSourceBucketName(bucketName);
        copyObjectRequest.setDestinationBucketName(destBucketName);
        copyObjectRequest.setSourceKey(fileName);
        copyObjectRequest.setDestinationKey(destFileName);
        amazonS3.copyObject(copyObjectRequest);
    }

    @SneakyThrows
    public OssFile statFile(String fileName) {
        return statFile(ossProperties.getBucketName(), fileName);
    }

    @SneakyThrows
    public OssFile statFile(String bucketName, String fileName) {
        ObjectMetadata objectMetadata = amazonS3.getObjectMetadata(bucketName, fileName);
        OssFile ossFile = new OssFile();
        ossFile.setName(fileName);
        ossFile.setLink(fileLink(ossFile.getName()));
        ossFile.setHash(objectMetadata.getContentMD5());
        ossFile.setLength(objectMetadata.getContentLength());
        ossFile.setPutTime(objectMetadata.getLastModified());
        ossFile.setContentType(objectMetadata.getContentType());
        return ossFile;
    }

    @SneakyThrows
    public String filePath(String fileName) {
        return getOssHost().concat(StrPool.SLASH).concat(fileName);
    }

    @SneakyThrows
    public String filePath(String bucketName, String fileName) {
        return getOssHost(bucketName).concat(StrPool.SLASH).concat(fileName);
    }

    @SneakyThrows
    public String fileLink(String fileName) {
        return getOssHost().concat(StrPool.SLASH).concat(fileName);
    }

    @SneakyThrows
    public String fileLink(String bucketName, String fileName) {
        return getOssHost(bucketName).concat(StrPool.SLASH).concat(fileName);
    }

    @SneakyThrows
    public DbyFile putFile(MultipartFile file) {
        return putFile(ossProperties.getBucketName(), null, file.getOriginalFilename(), file);
    }

    @SneakyThrows
    public DbyFile putFile(String prefix, MultipartFile file) {
        return putFile(ossProperties.getBucketName(), prefix, file.getOriginalFilename(), file);
    }

    @SneakyThrows
    public DbyFile putFile(String prefix, String fileName, MultipartFile file) {
        return putFile(ossProperties.getBucketName(), prefix, fileName, file);
    }

    @SneakyThrows
    public DbyFile putFile(String prefix, String fileName, InputStream inputStream) {
        return putFile(ossProperties.getBucketName(), prefix, fileName, inputStream);
    }

    @SneakyThrows
    public DbyFile putFile(String bucketName, String prefix, String fileName, MultipartFile file) {
        return putFile(bucketName, prefix, fileName, file.getInputStream());
    }

    @SneakyThrows
    public DbyFile putFile(String bucketName, String prefix, String fileName, InputStream stream) {
        return put(bucketName, stream, prefix, fileName, false);
    }

    @SneakyThrows
    public DbyFile putFileCover(String prefix, String fileName, InputStream stream) {
        return put(ossProperties.getBucketName(), stream, prefix, fileName);
    }

    @SneakyThrows
    public DbyFile put(String bucketName, InputStream stream, String prefix, String key, boolean cover) {
        makeBucket(bucketName);
        String originalName = key;
        key = getFileName(prefix, key);

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(stream.available());
        Tika tika =new Tika();
        String contentType = tika.detect(stream);
        objectMetadata.setContentType(contentType);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, stream, objectMetadata);
        // Setting the read limit value to one byte greater than the size of stream will
        // reliably avoid a ResetException
        putObjectRequest.getRequestClientOptions().setReadLimit(Long.valueOf(stream.available()).intValue() + 1);

        // 覆盖上传
        if (cover) {
            amazonS3.putObject(putObjectRequest);
        } else {
            int retry = 0;
            PutObjectResult putObjectResult;
            do {
                putObjectResult = amazonS3.putObject(putObjectRequest);
                retry++;
            } while (StrUtil.isEmpty(putObjectResult.getContentMd5()) && retry < 5);
        }
        DbyFile dbyFile = new DbyFile();
        dbyFile.setOriginalName(originalName);
        dbyFile.setName(key);
        dbyFile.setLink(fileLink(bucketName, key));
        return dbyFile;
    }

    @SneakyThrows
    public DbyFile put(String bucketName, InputStream stream, String prefix, String key) {
        makeBucket(bucketName);
        String originalName = key;
        key = prefix + StringPool.SLASH + key;

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentLength(stream.available());
        String contentType = HttpURLConnection.guessContentTypeFromStream(stream);
        objectMetadata.setContentType(contentType);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, stream, objectMetadata);
        // Setting the read limit value to one byte greater than the size of stream will
        // reliably avoid a ResetException
        putObjectRequest.getRequestClientOptions().setReadLimit(Long.valueOf(stream.available()).intValue() + 1);

        amazonS3.putObject(putObjectRequest);
        DbyFile dbyFile = new DbyFile();
        dbyFile.setOriginalName(originalName);
        dbyFile.setName(key);
        dbyFile.setLink(fileLink(bucketName, key));
        return dbyFile;
    }

    @SneakyThrows
    public void removeFile(String fileName) {
        amazonS3.deleteObject(ossProperties.getBucketName(), fileName);
    }

    @SneakyThrows
    public void removeFile(String bucketName, String fileName) {
        amazonS3.deleteObject(bucketName, fileName);
    }

    @SneakyThrows
    public void removeFiles(List<String> fileNames) {
        fileNames.forEach(this::removeFile);
    }

    @SneakyThrows
    public void removeFiles(String bucketName, List<String> fileNames) {
        fileNames.forEach(fileName -> removeFile(bucketName, fileName));
    }

    /**
     * 根据规则生成存储桶名称规则
     *
     * @return String
     */
    private String getBucketName() {
        return getBucketName(ossProperties.getBucketName());
    }

    /**
     * 根据规则生成存储桶名称规则
     *
     * @param bucketName 存储桶名称
     * @return String
     */
    private String getBucketName(String bucketName) {
        return ossRule.bucketName(bucketName);
    }

    /**
     * 根据规则生成文件名称规则
     *
     * @param prefix           前缀
     * @param originalFilename 原始文件名
     * @return string
     */
    private String getFileName(String prefix, String originalFilename) {
        return ossRule.fileName(prefix, originalFilename);
    }


    /**
     * 获取私有上传token
     *
     * @param objectName
     * @return
     */
    public Map<String, String> getPrivateUpTokenMap(String objectName) {
        URL url = generatePresignedUrl(ossProperties.getPrivateBucketName(), objectName, ossProperties.getExpireTime(), HttpMethod.PUT);
        Map<String, String> map = new HashMap<>();
        map.put("host", url.toString());
        map.put("url", objectName);
        return map;
    }


    /**
     * 获取签名链接
     *
     * @param key
     * @return
     */
    public URL generatePresignedUrl(String key) {
        return generatePresignedUrl(ossProperties.getPrivateBucketName(), key, ossProperties.getPreSignedUrlExpireTime(), HttpMethod.GET);
    }

    /**
     * <p>
     * 获取上传凭证，普通上传
     */
    public Map<String, String> getUploadTokenMap(String bucketName, String objectName) {
        URL objectURL = generatePresignedUrl(bucketName, objectName, ossProperties.getExpireTime(), HttpMethod.PUT);
        Map<String, String> map = new HashMap<>();
        map.put("host", objectURL.toString());
        map.put("url", getOssHost() + StringPool.SLASH + objectName);
        return map;
    }


    public String getOssHost(String bucketName) {
        String prefix = (StrUtil.isNotBlank(ossProperties.getDomain()) && ossProperties.getDomain().contains("https://")) ? "https://" : "http://";
        return prefix + (StrUtil.isBlank(ossProperties.getDomain()) ? getBucketName(bucketName) + StrPool.DOT + ossProperties.getEndpoint().replaceFirst(prefix, "") : ossProperties.getDomain().replace(prefix, ""));
    }

    public String getOssHost() {
        return getOssHost(ossProperties.getBucketName());
    }

    /**
     * 获取于url
     *
     * @param bucketName
     * @param key
     * @param httpMethod
     * @return
     */
    public URL generatePresignedUrl(String bucketName, String key, Long expireTime, HttpMethod httpMethod) {
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);
        if (!StringUtils.hasText(bucketName)) {
            bucketName = ossProperties.getBucketName();
        }
        return amazonS3.generatePresignedUrl(bucketName, key, expiration, httpMethod);
    }

}
